package com.mikepenz.iconics.utils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * A helper to covert Kotlin extensions to Java language.
 *
 * @since 2021-06-04
 */
public final class KtExtensions {
    private KtExtensions() {
    }

    /**
     * 有参无返回值回调
     *
     * @param <T>
     * @since 2021-06-04
     */
    public interface UnitBlock<T> {
        /**
         * Call this block function.
         *
         * @param invoker The invoker call the function which own this interface callback.
         */
        void invoke(T invoker);
    }

    /**
     * 有参有返回值回调
     *
     * @param <T>
     * @param <R>
     * @since 2021-06-04
     */
    public interface ResultBlockInvoker<T, R> {
        /**
         * Call this block function.
         *
         * @param invoker The invoker call the function which own this interface callback.
         * @return The result of this block function.
         */
        R invoke(T invoker);
    }

    /**
     * 无参有返回值回调
     *
     * @param <R>
     * @since 2021-06-04
     */
    public interface ResultBlockNoInvoker<R> {
        /**
         * Call this block function.
         *
         * @return The result of this block function.
         */
        R invoke();
    }

    /**
     * 有参有返回值回调
     *
     * @param <T>
     * @since 2021-06-04
     */
    public interface PredicateBlock<T> {
        /**
         * Call this block function.
         *
         * @param invoker The invoker call the function which own this interface callback.
         * @return A rule to match the result.
         */
        boolean predicate(T invoker);
    }

    /**
     * Calls the specified function block and returns its result.
     *
     * @param block The block function.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <R> R run(ResultBlockNoInvoker<R> block) {
        return block.invoke();
    }

    /**
     * Calls the specified function block with this value as its receiver and returns its result.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T> The type of invoker.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <T, R> R run(T invoker, ResultBlockInvoker<T, R> block) {
        return block.invoke(invoker);
    }

    /**
     * Calls the specified function block with the given receiver as its receiver and returns its result.
     * It is the same as {@link KtExtensions#run(Object, ResultBlockInvoker)} in java language.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T> The type of invoker.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <T, R> R with(T invoker, ResultBlockInvoker<T, R> block) {
        return block.invoke(invoker);
    }

    /**
     * Calls the specified function block with this value as its receiver and returns this value.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T> The type of invoker.
     * @return The result of this function.
     */
    public static <T> T apply(T invoker, UnitBlock<T> block) {
        block.invoke(invoker);
        return invoker;
    }

    /**
     * Calls the specified function block with this value as its argument and returns this value.
     * It is the same as {@link KtExtensions#apply(Object, UnitBlock)} in java language.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T> The type of invoker.
     * @return The result of this function.
     */
    public static <T> T also(T invoker, UnitBlock<T> block) {
        block.invoke(invoker);
        return invoker;
    }

    /**
     * Calls the specified function block with this value as its argument and returns its result.
     * It is the same as {@link KtExtensions#run(Object, ResultBlockInvoker)} in java language.
     *
     * @param invoker The invoker of this block function.
     * @param block The block function.
     * @param <T> The type of invoker.
     * @param <R> The type of result.
     * @return The result of this function.
     */
    public static <T, R> R let(T invoker, ResultBlockInvoker<T, R> block) {
        return block.invoke(invoker);
    }

    /**
     * Returns this value if it satisfies the given predicate or null, if it doesn't.
     *
     * @param invoker The invoker of this block function.
     * @param block A block function callback to invoke by the invoker.
     * @param <T> The type of invoker.
     * @return The invoker if match the result or null if not match.
     */
    public static <T> T takeIf(T invoker, PredicateBlock<T> block) {
        if (block.predicate(invoker)) {
            return invoker;
        } else {
            return null;
        }
    }

    /**
     * Returns this value if it does not satisfy the given predicate or null, if it does.
     *
     * @param invoker The invoker of this block function.
     * @param block A block function callback to invoke by the invoker.
     * @param <T> The type of invoker.
     * @return Null if match the result or the invoker if not match.
     */
    public static <T> T takeUnless(T invoker, PredicateBlock<T> block) {
        if (!block.predicate(invoker)) {
            return invoker;
        } else {
            return null;
        }
    }

    /**
     * Executes the given function action specified number of times.
     *
     * @param times The repeat count.
     * @param block The action to do.
     */
    public static void repeat(int times, UnitBlock<Integer> block) {
        for (int index = 0; index < times; index++) {
            block.invoke(index);
        }
    }

    /**
     * Simply to do repeat action for collection.
     *
     * @param collection The collection to foreach.
     * @param block The action to do.
     * @param <E> The type of element of collection.
     */
    public static <E> void forEach(Collection<E> collection, UnitBlock<E> block) {
        for (E element : collection) {
            block.invoke(element);
        }
    }

    /**
     * Returns a list containing the results of applying the given transform function
     * to each element in the original collection.
     *
     * @param collection Original collections to transform.
     * @param block The transform action to do.
     * @param <E> The type of element of original collection.
     * @param <R> The type of element of result collection.
     * @return The list after transform.
     */
    public static <E, R> List<R> map(Collection<E> collection, ResultBlockInvoker<E, R> block) {
        List<R> result = new ArrayList<>();
        for (E element : collection) {
            // do transform from E to R
            R rr = block.invoke(element);
            result.add(rr);
        }
        return result;
    }
}
